package br.org.bertol.mestrado.engine;

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;

/**
 * Enum com os objetivos.
 * @author contaqualquer
 */
public enum Objective {
    /**
     * Objetivo Dependência.
     */
    DEPEDENCIA {

        /***/
        private List<List<Integer>> weekMatrix = new ArrayList<List<Integer>>();

        @SuppressWarnings("unchecked")
        @Override
        public List<List<Integer>> getParameters() {
            return weekMatrix;
        }

        @Override
        public void loadParameters(final String source, final String system)
                throws IOException {

            weekMatrix = readInput(source, system, "_depe.txt");
        }

        @Override
        public int getConstraintHeight(final int classe,
                final int constraint) {
            return 1;
        }

        @Override
        public int getConstraintHeight(final List<String> operadores) {
            return 0;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<Integer> getInfo() {
            return new ArrayList<Integer>();
        }

        @Override
        public String formatValue(final Number input) {
            return input.toString();
        }

    },
    /**
     * Objetivo Método.
     */
    METODO {

        /***/
        private List<List<Integer>> weekMatrix = new ArrayList<List<Integer>>();

        @SuppressWarnings("unchecked")
        @Override
        public List<List<Integer>> getParameters() {
            return weekMatrix;
        }

        @Override
        public void loadParameters(final String source, final String system)
                throws IOException {

            weekMatrix = readInput(source, system, "_meth.txt");
        }

        @Override
        public int getConstraintHeight(final int classe,
                final int constraint) {
            return weekMatrix.get(classe - 1).get(constraint);
        }

        @Override
        public int getConstraintHeight(final List<String> operadores) {
            return 0;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<Integer> getInfo() {
            return new ArrayList<Integer>();
        }

        @Override
        public String formatValue(final Number input) {
            return input.toString();
        }
        },
    /**
     * Objetivo Atributo.
     */
    ATRIBUTO {

        /***/
        private List<List<Integer>> weekMatrix = new ArrayList<List<Integer>>();

        @SuppressWarnings("unchecked")
        @Override
        public List<List<Integer>> getParameters() {
            return weekMatrix;
        }

        @Override
        public void loadParameters(final String source, final String system)
                throws IOException {

            weekMatrix = readInput(source, system, "_attr.txt");
        }

        @Override
        public int getConstraintHeight(final int classe,
                final int constraint) {
            return weekMatrix.get(classe - 1).get(constraint);
        }

        @Override
        public int getConstraintHeight(final List<String> operadores) {
            return 0;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<Integer> getInfo() {
            return new ArrayList<Integer>();
        }

        @Override
        public String formatValue(final Number input) {
            return input.toString();
        }
        },
    /**
     * Objetivo Mutante.
     */
    MUTANTES {

        /***/
        private Map<String, ArrayList<Integer>> mutMutantPerOperator =
                                                                             new LinkedHashMap<String, ArrayList<Integer>>();
            /***/
        private Map<String, ArrayList<Integer>> mutCasePerOperator   = new LinkedHashMap<String, ArrayList<Integer>>();

        /***/
        private ArrayList<String>               cases                = new ArrayList<String>();

        @SuppressWarnings("unchecked")
        @Override
        public List<List<String>> getParameters() {
            final List<List<String>> f = new ArrayList<List<String>>();

            final ArrayList<String> d = new ArrayList<String>();

            for (final String key : mutMutantPerOperator.keySet()) {
                d.add(key);

            }

            f.add(d);

            return f;
        }

        /**
         * Realiza a soma de operadores.
         * @param operadores
         *            O operdor
         * @return Quantos mutantes tem
         */
        public int getConstraintHeight(final List<String> operadores) {

            int val = 0;

            cases.clear();

            for (final String operador : operadores) {

                // soma o total de mutantes
                final List<Integer> mutantes = mutMutantPerOperator
                        .get(operador);

                val += mutantes.size();

                // adiciona os casos de teste
                for (final Integer caseTest : mutCasePerOperator.get(operador)) {

                    if (!cases.contains(caseTest.toString())) {
                        cases.add(caseTest.toString());
                    }

                }

            }

            return val;
        }

        @Override
        public int getConstraintHeight(final int operador,
                final int constraint) {

            return 0;
        }

        @Override
        public void loadParameters(final String source, final String system)
                throws IOException {

            mutMutantPerOperator = readInputMutantPerOperator(source, system);

            mutCasePerOperator = readInputCasePerOperator(source, system);

        }

        @SuppressWarnings("unchecked")
        @Override
        public List<String> getInfo() {
            return (List<String>) cases.clone();
        }

        @Override
        public String formatValue(final Number input) {

            final NumberFormat format = new DecimalFormat("0000");

            return format.format(input.intValue());
        }
        },

    /**
     * Objetivo Cobertura.
     */
    COBERTURA {

        /***/
        private Map<String, ArrayList<Integer>> mutantPerCase           = new LinkedHashMap<String, ArrayList<Integer>>();

        /***/
        private Map<String, ArrayList<Integer>> cobertMutantPerOperator = new LinkedHashMap<String, ArrayList<Integer>>();

        /***/
        private transient int                   totalMutant;

        /***/
        private transient ArrayList<String>     efectiveCases           = new ArrayList<String>();

        @SuppressWarnings("unchecked")
        @Override
        public List<List<String>> getParameters() {

            final List<List<String>> f = new ArrayList<List<String>>();

            final ArrayList<String> d = new ArrayList<String>();

            for (final String key : cobertMutantPerOperator.keySet()) {
                d.add(key);

            }

            f.add(d);

            return f;
        }

        @Override
        public int getConstraintHeight(final int classe,
                final int constraint) {
            return 0;
        }

        @Override
        public void loadParameters(final String source, final String system)
                throws IOException {

            mutantPerCase = readInputMutantPerCase(source, system);

            cobertMutantPerOperator =
                    readInputMutantPerOperator(source, system);

            totalMutant = 0;

            for (String key : cobertMutantPerOperator.keySet()) {
                totalMutant += cobertMutantPerOperator.get(key).size();
            }

            // totalMutant += 1;

        }

        @Override
        public int getConstraintHeight(final List<String> testCases) {

            int totalKills = 0;

            final boolean[] mutantControl = new boolean[totalMutant];

            // zera controle de mutantes
            Arrays.fill(mutantControl, true);

            efectiveCases.clear();

            // verifica a cobertura dos casos de teste selecionados
            for (final String testCase : mutantPerCase.keySet()) {

                if (testCases.contains(testCase)) {

                    for (final int deadMutant : mutantPerCase.get(testCase)) {
                        if (mutantControl[deadMutant]) {
                            mutantControl[deadMutant] = false;
                            totalKills += 1;

                            if (!efectiveCases.contains(testCase)) {
                                efectiveCases.add(testCase);
                            }
                        }

                    }
                }
            }

            return totalKills;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<String> getInfo() {
            return (List<String>) efectiveCases.clone();
        }

        @Override
        public String formatValue(final Number input) {

            final NumberFormat format = new DecimalFormat("0.000000000000000",
                    new DecimalFormatSymbols(Locale.US));

            float coverage = input.floatValue() / totalMutant;

            return format.format(coverage);
        }

    };

    /**
     * Lista de restrições fortes.
     * @param <T>
     *            Tipo da matrix.
     * @return Lista
     */
    public abstract <T> List<List<T>> getParameters();

    /**
     * Avalia o peso da violação de uma restrição forte, se ouver.
     * @param classe
     *            Classe a ser avaliada
     * @param constraint
     *            Classe restrição da uma determinada classe
     * @return O peso da violação
     */
    public abstract int getConstraintHeight(final int classe,
            int constraint);

    /**
     * Realiza a soma de operadores.
     * @param operadores
     *            O operdor
     * @return Quantos mutantes tem
     */
    public abstract int getConstraintHeight(final List<String> operadores);

    /**
     * Carrega os parâmetros de inicialização.
     * @param source
     *            Caminho de origem
     * @param system
     *            Sistema a ser carregado
     * @throws IOException
     *             Caso erro ao carregar
     */
    public abstract void loadParameters(final String source,
            final String system) throws IOException;

    /**
     * Recupera uma informação qualquer do enum.
     * @param <T>
     *            Tipo a ser retornado.
     * @return T[] Array de T
     */
    public abstract <T> List<T> getInfo();

    /**
     * Formata a saída do objetivo.
     * @param input
     *            Entrada
     * @return Saída formatada
     */
    public abstract String formatValue(final Number input);

    /**
     * @param objectivesNames
     *            Lista de objetivos
     * @return Array com os objetivos passados como par�mtros.
     */
    public static Objective[] valueOf(final String... objectivesNames) {
        final Objective[] objectives = new Objective[objectivesNames.length];

        for (int i = 0; i < objectivesNames.length; i++) {
            objectives[i] = Objective.valueOf(objectivesNames[i]);
        }

        return objectives;
    }

    /**
     * Le as restrições fortes para problema de classes.
     * @param source
     *            Caminho de entrada
     * @param system
     *            Sistema
     * @return Restrições
     * @throws IOException
     *             Erro ao ler arquivo
     */
    public static List<List<Integer>> loadStrongConstraint(final String source,
            final String system) throws IOException {
        return readInput(source, system, "_prece.txt");
    }

    /**
     * Lêe arquivo com as entradas.
     * @param source
     *            Caminho onde está o arquivo
     * @param system
     *            Sistema a ser lido
     * @param typeInput
     *            Tipo de entrada
     * @return Dados de entrada
     * @throws IOException
     *             Erro ao ler arquivo
     */
    protected static List<List<Integer>> readInput(final String source,
            final String system, final String typeInput) throws IOException {
        final List<List<Integer>> colluns = new ArrayList<List<Integer>>();

        final File inputFile = new File(source + "/" + system + typeInput);

        if (!inputFile.exists()) {
            return colluns;
        }

        final Scanner reader = new Scanner(inputFile);

        // caminha pela matriz de entrada
        while (reader.hasNext()) {
            final String[] input = reader.nextLine().split(" ");

            final List<Integer> row = new ArrayList<Integer>();

            // adiciona as classes a uma lista
            for (String c : input) {
                row.add(Integer.parseInt(c));
            }
            colluns.add(row);
        }

        return colluns;
    }

    /**
     * Realiza a leitura de mutantes por operador.
     * @param source
     *            Caminho
     * @param system
     *            Arquivo
     * @return Matrix
     * @throws IOException
     *             Se der erro
     */
    protected static Map<String, ArrayList<Integer>> readInputMutantPerOperator(
            final String source,
            final String system) throws IOException {

        final Map<String, ArrayList<Integer>> map =
                new LinkedHashMap<String, ArrayList<Integer>>();

        final File inputFile = new File(source + "/" + system + ".txt");

        if (!inputFile.exists()) {
            return map;
        }

        final Scanner readerOperador = new Scanner(inputFile);

        final List<String> operadores = new ArrayList<String>();

        // caminha pela matriz de entrada e pega todos operadores
        if (readerOperador.hasNext()) {

            final String[] input = readerOperador.nextLine().split(";");

            for (int i = 1; i < input.length; i++) {

                if (!operadores.contains(input[i].toUpperCase())) {
                    operadores.add(input[i].toUpperCase());
                }

            }

        }

        readerOperador.close();

        // verifica quais mutantes são de qual operador
        final Scanner readerMutant = new Scanner(inputFile);

        // pega a lsta de operadores
        final String[] inputOper = readerMutant.nextLine().split(";");

        for (final String operador : operadores) {

            final ArrayList<Integer> row = new ArrayList<Integer>();

            for (int i = 1; i < inputOper.length; i++) {

                if (operador.equalsIgnoreCase(inputOper[i])) {
                    row.add(i - 1);
                }

            }

            map.put(operador, row);
        }

        readerMutant.close();

        return map;
    }

    /**
     * Retorna a matrix com o total de mutantes por caso de teste.
     * @param source
     *            Caminho até a entrada
     * @param system
     *            Nome do arquivo
     * @return Matrix de {@link List} conetendo os mutantes por caso de teste
     * @throws IOException
     *             Erro ao ler arquivo
     */
    protected static Map<String, ArrayList<Integer>> readInputMutantPerCase(
            final String source,
            final String system) throws IOException {

        final Map<String, ArrayList<Integer>> map =
                new LinkedHashMap<String, ArrayList<Integer>>();

        final File inputFile = new File(source + "/" + system + ".txt");

        if (!inputFile.exists()) {
            return map;
        }

        final Scanner readerCase = new Scanner(inputFile);

        // pula linha dos operadores
        readerCase.nextLine();

        // pula linha dos mutantes
        readerCase.nextLine();

        while (readerCase.hasNext()) {

            final ArrayList<Integer> row = new ArrayList<Integer>();

            final String[] inputMutantPerCase = readerCase.nextLine()
                    .split(";");

            // pega todos os mutantes mortos por um caso de teste
            for (int pointer = 1; pointer < inputMutantPerCase.length; pointer++) {

                // caso o mutante do operador seja morto pelo caso de teste
                if (Integer.parseInt(inputMutantPerCase[pointer]) == 1
                        && !row.contains(pointer - 1)) {

                    row.add(pointer - 1);
                }

            }

            map.put(inputMutantPerCase[0], row);
        }

        // fecha arquivo
        readerCase.close();

        return map;
    }

    /**
     * Retorna a matrix com o total de casos de teste por operador.
     * @param source
     *            Caminho até a entrada
     * @param system
     *            Nome do arquivo
     * @return Matrix de {@link List} conetendo os casos de teste por operador
     * @throws IOException
     *             Erro ao ler arquivo
     */
    protected static Map<String, ArrayList<Integer>> readInputCasePerOperator(
            final String source,
            final String system) throws IOException {

        final Map<String, ArrayList<Integer>> casesPerOperator =
                new LinkedHashMap<String, ArrayList<Integer>>();

        // pega mutantes por caso de teste
        final Map<String, ArrayList<Integer>> mutantPerCases =
                readInputMutantPerCase(source, system);

        // pega os mutantes por operador
        final Map<String, ArrayList<Integer>> mutantPerOperator =
                readInputMutantPerOperator(source, system);

        int maxM = 0;

        for (final String key : mutantPerOperator.keySet()) {

            final ArrayList<Integer> row = new ArrayList<Integer>();

            row.addAll(mutantPerOperator.get(key));

            maxM += row.size();

        }

        // pega todos os mutantes de um operador
        for (final String operador : mutantPerOperator.keySet()) {

            final boolean[] mutantControl = new boolean[maxM];

            Arrays.fill(mutantControl, true);

            final ArrayList<Integer> row = new ArrayList<Integer>();

            // pega a listade operadores e seus mutantes
            final List<Integer> mutantsPerOper = mutantPerOperator
                    .get(operador);

            // pega todos os mutantes mortos por um caso de teste
            for (final String caseTest : mutantPerCases.keySet()) {

                final Iterator<Integer> mutantPerCase =
                        mutantPerCases.get(caseTest).listIterator();

                while (mutantPerCase.hasNext()) {

                    final Integer mutant = mutantPerCase.next();

                    // caso o mutante do operador seja morto pelo caso de teste
                    if (mutantsPerOper.contains(mutant)
                            && mutantControl[mutant]) {

                        mutantControl[mutant] = false;

                        if (!row.contains(Integer.parseInt(caseTest))) {
                            row.add(Integer.parseInt(caseTest));
                        }

                    }
                }

            }

            casesPerOperator.put(operador, row);
        }

        return casesPerOperator;
    }
}
